﻿namespace Okazuki.MVVM.PrismSupport.ViewModels.Utils
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Practices.Prism.Commands;

    /// <summary>
    /// DelegateCommandを作成する
    /// </summary>
    internal static class DelegateCommandFactory
    {
        private static readonly Func<bool> TrueDelegate = () => true;
        private static readonly Func<object, bool> ParamTrueDelegate = o => true;

        // コマンド引数の型ごとのコマンド、Execute、CanExecuteのデリゲートの型のキャッシュ
        private static readonly Dictionary<Type, Tuple<Type, Type, Type>> CommandTypeCache = new Dictionary<Type, Tuple<Type, Type, Type>>();

        static DelegateCommandFactory()
        {
            // パラメータが無い場合のデリゲートの型のセットを設定する
            CommandTypeCache[typeof(void)] = Tuple.Create(
                typeof(DelegateCommand),
                typeof(Action),
                typeof(Func<bool>));
        }

        internal static void CreateAndSetCommand(ViewModelBase viewModel, ViewModelCommandInfo info)
        {
            var paramType = info.CommandProperty.PropertyType.GetGenericArguments().FirstOrDefault() ?? typeof(void);
            var typeSet = GetCommandTypes(paramType);
            Delegate canExecute = paramType == typeof(void) ? (Delegate)TrueDelegate : (Delegate)ParamTrueDelegate;
            if (info.CanExecuteMethod != null)
            {
                canExecute = Delegate.CreateDelegate(
                    typeSet.Item3, viewModel, info.CanExecuteMethod);
            }

            var command = Activator.CreateInstance(
                typeSet.Item1,
                Delegate.CreateDelegate(
                    typeSet.Item2, viewModel, info.ExecuteMethod),
                canExecute);
            info.CommandProperty.SetValue(viewModel, command, null);
        }

        private static Tuple<Type, Type, Type> GetCommandTypes(Type paramType)
        {
            lock (CommandTypeCache)
            {
                if (CommandTypeCache.ContainsKey(paramType))
                {
                    return CommandTypeCache[paramType];
                }
                var result = Tuple.Create(
                    typeof(DelegateCommand<>).MakeGenericType(paramType),
                    typeof(Action<>).MakeGenericType(paramType),
                    typeof(Func<,>).MakeGenericType(paramType, typeof(bool)));
                CommandTypeCache.Add(paramType, result);
                return result;
            }
        }
    }
}